package com.util;

import java.io.*;
import java.util.*;

import com.alibaba.fastjson.JSONObject;
import org.apache.poi.ooxml.POIXMLDocument;
import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.converter.pdf.PdfConverter;
import org.apache.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

/**
 *
 */
public class Word2Util {


    private Word2Util() {}

    private static class SingletonInstance {
        private static final Word2Util INSTANCE = new Word2Util();
    }

    public static Word2Util getInstance() {
        return SingletonInstance.INSTANCE;
    }

    /**
     * docx转pdf
     *
     * @param inPath
     * @param outPath
     * @throws IOException
     */
    public void docxToPDFConverter(String inPath, String outPath) throws IOException {
        InputStream inStream = Utils.getInFileStream(inPath);
        OutputStream outStream = Utils.getOutFileStream(outPath);

        XWPFDocument document = new XWPFDocument(inStream);
        PdfOptions options = PdfOptions.create();
        PdfConverter.getInstance().convert(document, outStream, options);
    }

    /**
     * 读取Word
     * @param inputUrl
     * @throws IOException
     * @throws OpenXML4JException
     * @throws XmlException
     */
    public String reeadWord(String inputUrl) throws IOException, OpenXML4JException, XmlException {
        OPCPackage opcPackage = POIXMLDocument.openPackage(inputUrl);
        POIXMLTextExtractor extractor = new XWPFWordExtractor(opcPackage);

        String text = extractor.getText();
        extractor.close();

        return text;
    }


    /**
     * 读取Word获取表达式
     *
     * @param inputUrl
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws TransformerException
     */
    public Map<String, Object> getExpData(String inputUrl) throws
            IOException, ParserConfigurationException, TransformerException, OpenXML4JException, XmlException {

        //解析docx模板并获取document对象
        XWPFDocument document = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));

        //获取整个文本对象
        List<XWPFParagraph> allParagraph = document.getParagraphs();
        List<String> textList = new ArrayList<>();
        //获取XWPFRun对象输出整个文本内容
        for (XWPFParagraph xwpfParagraph : allParagraph) {
            List<XWPFRun> runList = xwpfParagraph.getRuns();
            for (XWPFRun xwpfRun : runList) {
                String str = xwpfRun.toString();
                if (Utils.isExp(str)) {
                    textList.add(str);
                }
            }
        }

        Map<String, JSONObject> tableObj = new LinkedHashMap<>();

        //获取全部表格对象
        int tableIndex = 0;
        List<XWPFTable> allTable = document.getTables();

        for (XWPFTable xwpfTable : allTable) {
            //获取表格行数据
            List<XWPFTableRow> rows = xwpfTable.getRows();
            int rowIndex = 0;
            JSONObject table = new JSONObject();
            for (XWPFTableRow xwpfTableRow : rows) {
                // 获取一行中的所有列
                List<XWPFTableCell> cells = xwpfTableRow.getTableCells();

                List<String> rowInfo = new LinkedList<>();
                for (XWPFTableCell xwpfTableCell : cells) {
                    // 获取一格里的文本
                    String text = xwpfTableCell.getText();

                    if (Utils.isExp(text)) {
                        rowInfo.add(text);
                    }

//                    // 获取一格里的内容
//                    List<XWPFParagraph> paragraphs = xwpfTableCell.getParagraphs();
//                    for (XWPFParagraph xwpfParagraph : paragraphs) {
//                        List<XWPFRun> runs = xwpfParagraph.getRuns();
//                        rowInfo = new LinkedList<>();
//                        for (int i = 0; i < runs.size(); i++) {
//                            XWPFRun run = runs.get(i);
//                            String str = run.toString();
//                            if (Utils.isExp(str)) {
//                                rowInfo.add(str);
//                            }
//                        }
//                    }
                }
                if (!rowInfo.isEmpty()) {
                    table.put(String.valueOf(rowIndex), rowInfo);
                }
                rowIndex++;
            }
            tableObj.put(String.valueOf(tableIndex), table);
            tableIndex++;
        }

        Map<String, Object> result = new HashMap<>();
        result.put("text", textList);
        result.put("table", tableObj);

        System.out.println(result);

        return result;
    }

    /**
     * 根据模板生成新word文档
     * 判断表格是需要替换还是需要插入，判断逻辑有$为替换，表格无$为插入
     *
     * @param inputUrl  模板存放地址
     * @param outputUrl 新文档存放地址
     * @param textMap   需要替换的信息集合
     * @param tableList 需要插入的表格信息集合
     * @return 成功返回true, 失败返回false
     */
    public static boolean changWord(String inputUrl, String outputUrl,
                                    Map<String, String> textMap, List<String[]> tableList) {

        //模板转换默认成功
        boolean changeFlag = true;
        try {
            //获取docx解析对象
            XWPFDocument document = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));
            //解析替换文本段落对象
            Word2Util.changeText(document, textMap);
            //解析替换表格对象
            Word2Util.changeTable(document, textMap, tableList);

            //生成新的word
            File file = new File(outputUrl);
            FileOutputStream stream = new FileOutputStream(file);
            document.write(stream);
            stream.close();

        } catch (IOException e) {
            e.printStackTrace();
            changeFlag = false;
        }

        return changeFlag;

    }

    /**
     * 替换段落文本
     *
     * @param document docx解析对象
     * @param textMap  需要替换的信息集合
     */
    public static void changeText(XWPFDocument document, Map<String, String> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();

        for (XWPFParagraph paragraph : paragraphs) {
            //判断此段落时候需要进行替换
            String text = paragraph.getText();
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    run.setText(changeValue(run.toString(), textMap), 0);
                }
            }
        }

    }

    /**
     * 替换表格对象方法
     *
     * @param document  docx解析对象
     * @param textMap   需要替换的信息集合
     * @param tableList 需要插入的表格信息集合
     */
    public static void changeTable(XWPFDocument document, Map<String, String> textMap,
                                   List<String[]> tableList) {
        //获取表格对象集合
        List<XWPFTable> tables = document.getTables();
        for (int i = 0; i < tables.size(); i++) {
            //只处理行数大于等于2的表格，且不循环表头
            XWPFTable table = tables.get(i);
            if (table.getRows().size() > 1) {
                //判断表格是需要替换还是需要插入，判断逻辑有$为替换，表格无$为插入
                if (checkText(table.getText())) {
                    List<XWPFTableRow> rows = table.getRows();
                    //遍历表格,并替换模板
                    eachTable(rows, textMap);
                } else {
//                  System.out.println("插入"+table.getText());
                    insertTable(table, tableList);
                }
            }
        }
    }

    /**
     * 遍历表格
     *
     * @param rows    表格行对象
     * @param textMap 需要替换的信息集合
     */
    public static void eachTable(List<XWPFTableRow> rows, Map<String, String> textMap) {
        for (XWPFTableRow row : rows) {
            List<XWPFTableCell> cells = row.getTableCells();
            for (XWPFTableCell cell : cells) {
                //判断单元格是否需要替换
                if (checkText(cell.getText())) {
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    for (XWPFParagraph paragraph : paragraphs) {
                        List<XWPFRun> runs = paragraph.getRuns();
                        for (XWPFRun run : runs) {
                            run.setText(changeValue(run.toString(), textMap), 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * 为表格插入数据，行数不够添加新行
     *
     * @param table     需要插入数据的表格
     * @param tableList 插入数据集合
     */
    public static void insertTable(XWPFTable table, List<String[]> tableList) {
        //创建行,根据需要插入的数据添加新行，不处理表头
        for (int i = 1; i < tableList.size(); i++) {
            XWPFTableRow row = table.createRow();
        }
        //遍历表格插入数据
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 1; i < rows.size(); i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            for (int j = 0; j < cells.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                cell.setText(tableList.get(i - 1)[j]);
            }
        }

    }

    /**
     * 判断文本中时候包含$
     *
     * @param text 文本
     * @return 包含返回true, 不包含返回false
     */
    public static boolean checkText(String text) {
        boolean check = false;
        if (text.indexOf("$") != -1) {
            check = true;
        }
        return check;

    }

    /**
     * 匹配传入信息集合与模板
     *
     * @param value   模板需要替换的区域
     * @param textMap 传入信息集合
     * @return 模板需要替换区域信息集合对应值
     */
    public static String changeValue(String value, Map<String, String> textMap) {
        Set<Map.Entry<String, String>> textSets = textMap.entrySet();
        for (Map.Entry<String, String> textSet : textSets) {
            //匹配模板与替换值 格式${key}
            String key = "${" + textSet.getKey() + "}";
            if (value.indexOf(key) != -1) {
                value = textSet.getValue();
            }
        }
        //模板未匹配到区域替换为空
        if (checkText(value)) {
            value = "";
        }
        return value;
    }


}

